Expand description
Defined Traits for embedded-registers-derive. For Derive Docs see embedded-registers-derive.
This crate provides a procedural macro for effortless definitions of registers in embedded device drivers. This is automatically generates functions to read/write the register over I2C and SPI, although it isn’t limited to those buses. The resulting struct may trivially be extended to work with any other similar communication bus.
- Allows defintion of read-only, write-only and read-write registers
- Generates I2C and SPI read/write functions
- Registers are defined as bitfields via bondrewd.
- Only the accessed bitfield members are decoded, conserving memory and saving on CPU time.
- Supports both async and blocking operation modes
This crate was made primarily for embedded-devices, which is a collection of drivers for a variety of different embedded sensors and devices.
§Defining a register
Registers are defined simply by annotating a bondrewd struct with #[register(address = 0x42, mode = "rw")]
.
The necessary derive attribute is added automatically.
Take for example this 2-byte read-write register at device address 0x42,0x43
, which contains two u8
values:
#![feature(generic_arg_infer)]
use embedded_registers::register;
#[register(address = 0x42, mode = "rw")]
#[bondrewd(read_from = "msb0", default_endianness = "be", enforce_bytes = 2)]
pub struct ValueRegister {
pub width: u8,
pub height: u8,
}
This will create two structs called ValueRegister
and ValueRegisterBitfield
.
The first will only contain a byte array [u8; 2]
to store the packed register contents,
and the latter will contain the unpacked actual members as defined above.
You will always interface with a device using the packed data, which can be transferred over the bus as-is.
The packed data contains methods to directly read/write the underlying storage array, which means you can only unpack what you need, saving resources.
I find it a bit misleading that the members written in
ValueRegister
end up inValueRegisterBitfield
. So this might change in the future, but I currently cannot think of another design that is as simple to use as this one right now. The issue is that we need a struct for the packed data and one for the unpacked data. Since we usually deal with the packed data, and want to allow direct read/write operations on the packed data for performance, the naming gets confusing quite quickly.
§Accessing a register
To access such a register, you need to obtain an interface that implements the RegisterInterface
trait.
This crate already comes with an implementation of that trait for I2C and SPI devices called i2c::I2cDevice
and spi::SpiDevice
respectively.
You may then read the register simply by calling read_register
or write_register
on that interface.
use embedded_registers::{i2c::{I2cDevice, codecs::OneByteRegAddrCodec}, RegisterInterface};
async fn init<I>(mut i2c_bus: I) -> Result<(), I::Error>
where
I: embedded_hal_async::i2c::I2c + embedded_hal_async::i2c::ErrorType,
{
// Imagine we already have constructed a device using
// the i2c bus from your controller, a device address and default codec:
let mut dev = I2cDevice::new(i2c_bus, 0x12, OneByteRegAddrCodec::default());
// We can now retrieve the register
let mut reg = dev.read_register::<ValueRegister>().await?;
// Unpack a specific field from the register and print it
println!("{}", reg.read_width());
// If you need all fields (or are not bound to tight resource constraints),
// you can also unpack all fields and access them more conveniently
let data = reg.read_all();
// All bitfields implement Debug and defmt::Format, so you can conveniently
// print the contents
println!("{:?}", data);
// We can also change a single value
reg.write_height(190);
// Or pack a bitfield and replace everything
reg.write_all(data); // same as reg = ValueRegister::new(data);
// Which we can now write back to the device, given that the register is writable.
dev.write_register(reg).await?;
Ok(())
}
Re-exports§
Modules§
Traits§
- This trait is a marker trait implemented by any register that can be read via a specific bus interface.
- The basis trait for all registers. A register is a type that maps to a specific register on an embedded device and should own the raw data required for this register.
- A trait that is implemented by any bus interface and allows devices with registers to share register read/write implementations independent of the actual interface in use.
- This trait is a marker trait implemented by any register that can be written via a specific bus interface.